package fr.mywiki.view.web.struts.action.modeling;

import java.io.IOException;
import java.rmi.RemoteException;
import java.sql.SQLException;
import java.text.ParseException;
import java.util.Date;

import javax.ejb.ObjectNotFoundException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.struts.action.ActionForm;
import org.apache.struts.action.ActionForward;
import org.apache.struts.action.ActionMapping;
import org.apache.struts.action.ActionMessage;
import org.apache.struts.action.ActionMessages;
import org.apache.struts.actions.DispatchAction;
import org.jdom.JDOMException;

import fr.mywiki.business.entry.meta.DataType;
import fr.mywiki.business.entry.meta.EntryModel;
import fr.mywiki.business.entry.meta.EntryModelManager;
import fr.mywiki.business.entry.meta.ModelException;
import fr.mywiki.business.entry.meta.Property;
import fr.mywiki.business.entry.meta.datatype.Choice;
import fr.mywiki.business.entry.meta.datatype.DataTypeManager;
import fr.mywiki.business.entry.meta.datatype.DateProperty;
import fr.mywiki.business.entry.meta.datatype.EntryLink;
import fr.mywiki.business.entry.meta.datatype.number.IntNumber;
import fr.mywiki.business.entry.meta.datatype.number.RealNumber;
import fr.mywiki.business.entry.meta.datatype.text.SimpleText;
import fr.mywiki.business.entry.meta.datatype.text.WikiText;
import fr.mywiki.business.resources.ResourceManager;
import fr.mywiki.business.utils.DateUtils;
import fr.mywiki.business.utils.StringUtils;
import fr.mywiki.business.xml.AJAXManager;
import fr.mywiki.model.ejbAdapter.DataController;
import fr.mywiki.model.ejbAdapter.ServicesFacade;
import fr.mywiki.model.ejbAdapter.utils.PersistenceException;
import fr.mywiki.view.web.struts.action.ListAction;
import fr.mywiki.view.web.struts.form.modeling.EditModelForm;

/**
 * Action dedicated to the creation of a model.
 * 
 * @author Thomas
 */
public class EditModelAction extends DispatchAction {
	public static final String PROPERTY_NAME_MANDATORY = "model.edit.property.noname";
	public static final String PROPERTY_NAME_NOT_UNIQUE = "model.edit.property.namenotunique";
	
	public static final String PROPERTY_TYPE_MANDATORY = "model.edit.property.notype";

	public static final String PROPERTY_SIMPLETEXT_NO_MAX = "model.edit.property.simpletext.nomax";

	public static final String PROPERTY_INT_NO_MIN = "model.edit.property.intnumber.nomin";
	public static final String PROPERTY_INT_NO_MAX = "model.edit.property.intnumber.nomax";
	public static final String PROPERTY_INT_BAD_ORDER = "model.edit.property.intnumber.badorder";

	public static final String PROPERTY_REAL_NO_MIN = "model.edit.property.realnumber.nomin";
	public static final String PROPERTY_REAL_NO_MAX = "model.edit.property.realnumber.nomax";
	public static final String PROPERTY_REAL_BAD_ORDER = "model.edit.property.realnumber.badorder";

	public static final String PROPERTY_DATE_NO_MIN = "model.edit.property.date.nomin";
	public static final String PROPERTY_DATE_NO_MAX = "model.edit.property.date.nomax";
	public static final String PROPERTY_DATE_BAD_ORDER = "model.edit.property.date.badorder";

	public static final String PROPERTY_CHOICE_NO_MIN = "model.edit.property.choice.nomin";
	public static final String PROPERTY_CHOICE_NO_MAX = "model.edit.property.choice.nomax";
	public static final String PROPERTY_CHOICE_BAD_ORDER = "model.edit.property.choice.badorder";

	public static final String PROPERTY_ENTRYLINK_NO_MIN = "model.edit.property.entrylink.nomin";
	public static final String PROPERTY_ENTRYLINK_NO_MAX = "model.edit.property.entrylink.nomax";
	public static final String PROPERTY_ENTRYLINK_BAD_ORDER = "model.edit.property.entrylink.badorder";

	/** Inits and displays the formular for the creation of a new model. */
	public ActionForward displayCreate(ActionMapping mapping,
			ActionForm actionForm, HttpServletRequest request,
			HttpServletResponse response) {
		EditModelForm form = (EditModelForm) actionForm;
		form.initCreate(ResourceManager.getLocale(request));
		return mapping.findForward("display");
	}

	/**
	 * Inits and displays the formular for the modification of a model whose
	 * identifier is given in parameter.
	 */
	public ActionForward displayModify(ActionMapping mapping,
			ActionForm actionForm, HttpServletRequest request,
			HttpServletResponse response) throws ObjectNotFoundException,
			RemoteException {
		EditModelForm form = (EditModelForm) actionForm;
		Long modelId = (Long) request.getAttribute(ListAction.ELEMENT_ID);
		EntryModel model = (EntryModel) ServicesFacade.getInstance().getValue(
				modelId, EntryModel.TYPE_ENT);
		form.initEdit(model, ResourceManager.getLocale(request));
		return mapping.findForward("display");
	}

	/** Cancel the edition of the formular. Return to the models list */
	public ActionForward cancel(ActionMapping mapping, ActionForm actionForm,
			HttpServletRequest request, HttpServletResponse response) {
		return mapping.findForward("modelsList");
	}

	/** Redisplays the formular */
	public ActionForward redisplay(ActionMapping mapping, ActionForm actionForm,
			HttpServletRequest request, HttpServletResponse response) {
		return mapping.findForward("display");
	}

	/** Validates the edition of the formular. Return to the models list. */
	public ActionForward validate(ActionMapping mapping, ActionForm actionForm,
			HttpServletRequest request, HttpServletResponse response)
			throws SQLException, ModelException, PersistenceException,
			RemoteException, ObjectNotFoundException, JDOMException {
		EditModelForm form = (EditModelForm) actionForm;

		String error = form.check();
		if (error != null) {
			ActionMessages errors = new ActionMessages();
			errors.add(ActionMessages.GLOBAL_MESSAGE, new ActionMessage(error));
			saveErrors(request, errors);
			return mapping.findForward("display");
		}

		if (EditModelForm.MODE_CREATION.equals(form.getMode())) {
			createModel(form);
		} else if (EditModelForm.MODE_EDITION.equals(form.getMode())) {
			updateModel(form);
		}
		return mapping.findForward("modelsList");
	}

	/**
	 * Creates a new model from the data contained in the formular.
	 * 
	 * @param form
	 *          a <code>EditModelForm</code> object.
	 */
	private void createModel(EditModelForm form) throws RemoteException,
			ObjectNotFoundException, PersistenceException, ModelException {
		EntryModelManager.createModel(form.getName(), form.getProperties());
	}

	/**
	 * Updates the model with the data contained in the formular.
	 * 
	 * @param form
	 *          a <code>EditModelForm</code> object.
	 */
	private void updateModel(EditModelForm form) throws ObjectNotFoundException,
			RemoteException, ModelException {
		EntryModel model = (EntryModel) DataController.getValue(form.getModelId(),
				EntryModel.TYPE_ENT);
		EntryModelManager.modifyModel(model, form.getName(),form.getProperties());
	}

	/** Launches the modification of the selected property. 
	 * @throws IOException */
	public ActionForward modifyProperty(ActionMapping mapping,
			ActionForm actionForm, HttpServletRequest request,
			HttpServletResponse response) throws IOException {
		EditModelForm form = (EditModelForm) actionForm;
		int propIndex = form.getSavedIndex();
		Property prop = (Property) form.getProperties().get(
				propIndex);
		
		StringBuffer ajaxReturn = new StringBuffer();
		ajaxReturn.append("{ ");
		ajaxReturn.append(" name : \"" + prop.getName() + "\"");
		
		DataType type =  prop.getType();
		String typeName = type.getTypeName();
		ajaxReturn.append(", type : \"" + typeName + "\"");
		
		String useMin = new String();
		String useMax = new String();
		String minVal = new String();
		String maxVal = new String();
		
		// use min ?
		if(SimpleText.TYPE_NAME.equals(typeName)) {
			useMin = "false";
			minVal = "";
			SimpleText cType = ((SimpleText)type);
			if(cType.isSizeLimited()) {
				useMax = "true";
				maxVal = cType.getMaxSize() + "";
			} else {
				useMax = "false";
				maxVal = "";
			}
		} else if(WikiText.TYPE_NAME.equals(typeName)) {
			useMin = "false";
			minVal = "";
			useMax = "false";
			maxVal = "";
		} else if(IntNumber.TYPE_NAME.equals(typeName)) {
			IntNumber cType = ((IntNumber)type);
			if(cType.getMin()!=null) {
				useMin = "true";
				minVal = cType.getMin().toString();
			} else {
				useMin = "false";
				minVal = "";
			}
			if(cType.getMax()!=null) {
				useMax = "true";
				maxVal = cType.getMax().toString();
			} else {
				useMax = "false";
				maxVal = "";
			}
		} else if(RealNumber.TYPE_NAME.equals(typeName)) {
			RealNumber cType = ((RealNumber)type);
			if(cType.getMin()!=null) {
				useMin = "true";
				minVal = cType.getMin().toString();
			} else {
				useMin = "false";
				minVal = "";
			}
			if(cType.getMax()!=null) {
				useMax = "true";
				maxVal = cType.getMax().toString();
			} else {
				useMax = "false";
				maxVal = "";
			}
		} else if(DateProperty.TYPE_NAME.equals(typeName)) {
			DateProperty cType = ((DateProperty)type);
			if(cType.getMinDate()!=null) {
				useMin = "true";
				minVal = DateUtils.formatDate(cType.getMinDate());
			} else {
				useMin = "false";
				minVal = "";
			}
			if(cType.getMaxDate()!=null) {
				useMax = "true";
				maxVal = DateUtils.formatDate(cType.getMaxDate());
			} else {
				useMax = "false";
				maxVal = "";
			}
		} else {
			useMin = "false";
			minVal = "";
			useMax = "false";
			maxVal = "";
		}
		
		ajaxReturn.append(", useMin : \"" + useMin + "\"");
		ajaxReturn.append(", minVal : \"" + minVal + "\"");
		ajaxReturn.append(", useMax : \"" + useMax + "\"");
		ajaxReturn.append(", maxVal : \"" + maxVal + "\"");
		
		ajaxReturn.append(", required : \"" + prop.isRequired().toString() + "\"");
		ajaxReturn.append(" }");

		AJAXManager.sendResponse(AJAXManager.JSON_CONTENT_TYPE, ajaxReturn.toString(), response);

		return null;
	}

	/** Removes the selected property. */
	public ActionForward removeProperty(ActionMapping mapping,
			ActionForm actionForm, HttpServletRequest request,
			HttpServletResponse response) {
		EditModelForm form = (EditModelForm) actionForm;
		int propIndex = form.getSavedIndex();
		form.getProperties().remove(propIndex);

		return mapping.findForward("display");
	}

	/**
	 * The user wants to add a potential value to a specific property.
	 * 
	 * TODO A revoir dans le cas EntryLink
	 * @throws IOException 
	 */
	public ActionForward addPotential(ActionMapping mapping,
			ActionForm actionForm, HttpServletRequest request,
			HttpServletResponse response) throws IOException {
		EditModelForm form = (EditModelForm) actionForm;
		String newPotString = form.getSearchString();
		
		form.getEditedPotentials().add(newPotString);
		
		String ajaxReturn = form.getEditedPotentialsString();
		
		AJAXManager.sendResponse(AJAXManager.TEXT_CONTENT_TYPE, ajaxReturn
				.toString(), response);
		
		return null;
	}

	private String editProperty(EditModelForm form, String action) {
		int propIndex = form.getSavedIndex();
		
		String name = form.getEditedName(); // Name
		String typeStr = form.getEditedType(); // Type
		String required = form.getEditedRequired(); // Required ?
		
		DataType type = null;
		
		StringBuffer ajaxReturn = new StringBuffer();
		
		//	 Check if the name is not empty
		if (StringUtils.isNullOrEmpty(name))
			ajaxReturn.append(ResourceManager.getError(PROPERTY_NAME_MANDATORY, form
					.getLocale()));
		// Check if the name is not already used
		else {
			if (EditModelForm.PROPERTY_ACTION_MODIFICATION.equals(action)
					&& !form.checkNameUnicity(propIndex, name))
				ajaxReturn.append(ResourceManager.getError(PROPERTY_NAME_NOT_UNIQUE, form
						.getLocale()));
			if (EditModelForm.PROPERTY_ACTION_CREATION.equals(action)
					&& !form.checkNameUnicity(name))
				ajaxReturn.append(ResourceManager.getError(PROPERTY_NAME_NOT_UNIQUE, form
						.getLocale()));
		}
		if (StringUtils.isNullOrEmpty(typeStr))
			ajaxReturn.append(ResourceManager.getError(PROPERTY_TYPE_MANDATORY, form
					.getLocale()));
		else {
			// Check and prepare datatype
			type = DataTypeManager.createDataType(typeStr);

			if (SimpleText.TYPE_NAME.equals(typeStr)) {
				// Simple text property. Max size ?
				boolean useMaxLength = "true".equals(form.getEditedUseMaxValue());
				if (useMaxLength) {
					int maxLength = SimpleText.NO_SIZE_LIMIT;
					try {
						maxLength = Integer.parseInt(form.getEditedMaxValue());
					} catch (NumberFormatException e) {
						ajaxReturn.append(ResourceManager.getError(
								PROPERTY_SIMPLETEXT_NO_MAX, form.getLocale()));
					}
					((SimpleText) type).setMaxSize(maxLength);
				}
			} else if (WikiText.TYPE_NAME.equals(typeStr)) {
				// Wiki text property. No conditions
			} else if (IntNumber.TYPE_NAME.equals(typeStr)) {
				// Int number. Do we use a min value ?
				boolean useMin = "true".equals(form.getEditedUseMinValue());
				Long minInt = null;
				if (useMin) {
					String minString = form.getEditedMinValue();
					if (StringUtils.isNullOrEmpty(minString)) {
						// Use min but no min value entered
						ajaxReturn.append(ResourceManager.getError(PROPERTY_INT_NO_MIN, form
								.getLocale()));
					}
					try {
						minInt = new Long(minString);
					} catch (NumberFormatException e) {
						ajaxReturn.append(ResourceManager.getError(PROPERTY_INT_NO_MIN, form
								.getLocale()));
					}
				}

				// Do we use a max value ?
				boolean useMax = "true".equals(form.getEditedUseMaxValue());
				Long maxInt = null;
				if (useMax) {
					String maxString = form.getEditedMaxValue();
					if (StringUtils.isNullOrEmpty(maxString)) {
						// Use max but no max value entered
						ajaxReturn.append(ResourceManager.getError(PROPERTY_INT_NO_MAX, form
								.getLocale()));
					}
					try {
						maxInt = new Long(maxString);
					} catch (NumberFormatException e) {
						// Invalid max value
						ajaxReturn.append(ResourceManager.getError(PROPERTY_INT_NO_MAX, form
								.getLocale()));
					}
				}

				if (useMin && useMax) {
					// Min must be less than max
					if (minInt.compareTo(maxInt) > 0) {
						ajaxReturn.append(ResourceManager.getError(PROPERTY_INT_BAD_ORDER,
								form.getLocale()));
					}
				}
				((IntNumber) type).setMin(minInt);
				((IntNumber) type).setMax(maxInt);
			} else if (RealNumber.TYPE_NAME.equals(typeStr)) {
				// Real number.
				// Do we use a min value ?
				boolean useMin = "true".equals(form.getEditedUseMinValue());
				Double minReal = null;
				if (useMin) {
					String minString = form.getEditedMinValue();
					if (StringUtils.isNullOrEmpty(minString)) {
						// Use min but no min value entered
						ajaxReturn.append(ResourceManager.getError(PROPERTY_REAL_NO_MIN,
								form.getLocale()));
					}
					try {
						minReal = new Double(minString);
					} catch (NumberFormatException e) {
						// Invalid min value
						ajaxReturn.append(ResourceManager.getError(PROPERTY_REAL_NO_MIN,
								form.getLocale()));
					}
				}

				// Do we use a max value ?
				boolean useMax = "true".equals(form.getEditedUseMaxValue());
				Double maxReal = null;
				if (useMax) {
					String maxString = form.getEditedMaxValue();
					if (StringUtils.isNullOrEmpty(maxString)) {
						// Use max but no max value entered
						ajaxReturn.append(ResourceManager.getError(PROPERTY_REAL_NO_MAX,
								form.getLocale()));
					}
					try {
						maxReal = new Double(maxString);
					} catch (NumberFormatException e) {
						// Invalid max value
						ajaxReturn.append(ResourceManager.getError(PROPERTY_REAL_NO_MAX,
								form.getLocale()));
					}
				}

				if (useMin && useMax) {
					// Min must be less than max
					if (minReal.compareTo(maxReal) > 0) {
						ajaxReturn.append(ResourceManager.getError(PROPERTY_REAL_BAD_ORDER,
								form.getLocale()));
					}
				}
				((RealNumber) type).setMin(minReal);
				((RealNumber) type).setMax(maxReal);
			} else if (DateProperty.TYPE_NAME.equals(typeStr)) {
				// Date property.
				// Do we use a min date ?
				boolean useMin = "true".equals(form.getEditedUseMinValue());
				Date minDate = null;
				if (useMin) {
					String minString = form.getEditedMinValue();
					if (StringUtils.isNullOrEmpty(minString)) {
						// Use min but no min date entered
						ajaxReturn.append(ResourceManager.getError(PROPERTY_DATE_NO_MIN,
								form.getLocale()));

					}
					try {
						minDate = DateUtils.parseDate(minString);
					} catch (ParseException e) {
						// Invalid min date
						ajaxReturn.append(ResourceManager.getError(PROPERTY_DATE_NO_MIN,
								form.getLocale()));
					}
				}

				// Do we use a max date ?
				boolean useMax = "true".equals(form.getEditedUseMaxValue());
				Date maxDate = null;
				if (useMax) {
					String maxString = form.getEditedMaxValue();
					if (StringUtils.isNullOrEmpty(maxString)) {
						// Use max but no max date entered
						ajaxReturn.append(ResourceManager.getError(PROPERTY_DATE_NO_MAX,
								form.getLocale()));
					}
					try {
						maxDate = DateUtils.parseDate(maxString);
					} catch (ParseException e) {
						// Invalid max date
						ajaxReturn.append(ResourceManager.getError(PROPERTY_DATE_NO_MAX,
								form.getLocale()));
					}
				}

				if (useMin && useMax) {
					// Min must be less than max
					if (minDate.after(maxDate)) {
						ajaxReturn.append(ResourceManager.getError(PROPERTY_DATE_BAD_ORDER,
								form.getLocale()));
					}
				}
				((DateProperty) type).setMinDate(minDate);
				((DateProperty) type).setMaxDate(maxDate);
			} else if (Choice.TYPE_NAME.equals(typeStr)) {
				// Choice property.
				// Do we use a min value ?
				boolean useMin = "true".equals(form.getEditedUseMinValue());
				Integer minInt = null;
				if (useMin) {
					String minString = form.getEditedMinValue();
					if (StringUtils.isNullOrEmpty(minString)) {
						ajaxReturn.append(ResourceManager.getError(PROPERTY_CHOICE_NO_MIN,
								form.getLocale()));
					}
					try {
						minInt = new Integer(minString);
					} catch (NumberFormatException e) {
						// Invalid min value
						ajaxReturn.append(ResourceManager.getError(PROPERTY_CHOICE_NO_MIN,
								form.getLocale()));
					}
				}

				// Do we use a max value ?
				boolean useMax = "true".equals(form.getEditedUseMaxValue());
				Integer maxInt = null;
				if (useMax) {
					String maxString = form.getEditedMaxValue();
					if (StringUtils.isNullOrEmpty(maxString)) {
						// Use max but no max value entered
						ajaxReturn.append(ResourceManager.getError(PROPERTY_CHOICE_NO_MAX,
								form.getLocale()));
					}
					try {
						maxInt = new Integer(maxString);
					} catch (NumberFormatException e) {
						// Invalid max value
						ajaxReturn.append(ResourceManager.getError(PROPERTY_CHOICE_NO_MAX,
								form.getLocale()));
					}
				}

				if (useMin && useMax) {
					// Min must be less than max
					if (minInt.compareTo(maxInt) > 0) {
						ajaxReturn.append(ResourceManager.getError(
								PROPERTY_CHOICE_BAD_ORDER, form.getLocale()));
					}
				}
				if (minInt != null)
					((Choice) type).setMinLinks(minInt.intValue());
				if (maxInt != null)
					((Choice) type).setMaxLinks(maxInt.intValue());

				// TODO fill the potentials
			} else if (EntryLink.TYPE_NAME.equals(typeStr)) {
				// Entry link property.
				// Do we use a min value ?
				boolean useMin = "true".equals(form.getEditedUseMinValue());
				Integer minInt = null;
				if (useMin) {
					String minString = form.getEditedMinValue();
					if (StringUtils.isNullOrEmpty(minString)) {
						// Use min but no min value entered
						ajaxReturn.append(ResourceManager.getError(
								PROPERTY_ENTRYLINK_NO_MIN, form.getLocale()));
					}
					try {
						minInt = new Integer(minString);
					} catch (NumberFormatException e) {
						// Invalid min value
						ajaxReturn.append(ResourceManager.getError(
								PROPERTY_ENTRYLINK_NO_MIN, form.getLocale()));
					}
				}

				// Do we use a max value ?
				boolean useMax = "true".equals(form.getEditedUseMaxValue());
				Integer maxInt = null;
				if (useMax) {
					String maxString = form.getEditedMaxValue();
					if (StringUtils.isNullOrEmpty(maxString)) {
						// Use max but no max value entered
						ajaxReturn.append(ResourceManager.getError(
								PROPERTY_ENTRYLINK_NO_MAX, form.getLocale()));
					}
					try {
						maxInt = new Integer(maxString);
					} catch (NumberFormatException e) {
						// Invalid max value
						ajaxReturn.append(ResourceManager.getError(
								PROPERTY_ENTRYLINK_NO_MAX, form.getLocale()));
					}
				}

				if (useMin && useMax) {
					// Min must be less than max
					if (minInt.compareTo(maxInt) > 0) {
						ajaxReturn.append(ResourceManager.getError(
								PROPERTY_ENTRYLINK_BAD_ORDER, form.getLocale()));
					}
				}
				if (minInt != null)
					((EntryLink) type).setMinLinks(minInt.intValue());
				if (maxInt != null)
					((EntryLink) type).setMaxLinks(maxInt.intValue());

				// TODO Fill the potentials
			}
		}
		
		if (ajaxReturn.length() == 0) {
			boolean req = !StringUtils.isNullOrEmpty(required);
			
			Property prop = null;
			if (EditModelForm.PROPERTY_ACTION_CREATION.equals(action)) {
				prop = new Property(name,req);
				form.getProperties().add(prop);
			} else {
				prop = (Property) form.getProperties().get(propIndex);
				prop.setName(name);
				prop.setRequired(req);
			}
			
			prop.setType(type);

			ajaxReturn.append("OK");
		}
		
		return ajaxReturn.toString();
	}
	
	/**
	 * Creates a new property, adds it to the list of properties and returns the ajax
	 * return string.
	 * 
	 * @return an empty String if everything was correct, an error text otherwise.
	 */
	private String createProperty(EditModelForm form) {
		return editProperty(form,EditModelForm.PROPERTY_ACTION_CREATION);
	}
	
	/**
	 * Modifies the desired property and returns the ajax return string.
	 * 
	 * @return an empty String if everything was correct, an error text otherwise.
	 */
	private String modifyProperty(EditModelForm form) {
		return editProperty(form,EditModelForm.PROPERTY_ACTION_MODIFICATION);
	}
	
	/**
	 * Validates the modifications done on the property (creation/modification) after
	 * having checked the formular.
	 * 
	 * @throws IOException
	 */
	public ActionForward validateProperty(ActionMapping mapping,
			ActionForm actionForm, HttpServletRequest request,
			HttpServletResponse response) throws IOException {
		EditModelForm form = (EditModelForm) actionForm;

		String propAction = form.getPropertyAction(); // Which action

		if (EditModelForm.PROPERTY_ACTION_CREATION.equals(propAction)) {
			AJAXManager.sendResponse(AJAXManager.TEXT_CONTENT_TYPE,
					createProperty(form), response);
		} else if (EditModelForm.PROPERTY_ACTION_MODIFICATION.equals(propAction)) {
			AJAXManager.sendResponse(AJAXManager.TEXT_CONTENT_TYPE,
					modifyProperty(form), response);
		}

		return null;
	}
}
